﻿using System;
using System.Collections.Generic;
using System.Linq;
using HPCloud.Objects.Domain.Compute.Servers.Actions;
using HPCloud.Objects.Domain.Compute.Servers;
using HPCloud.Common.Properties;
using System.Threading;
using HPCloud.Objects.Domain.Compute.Security;
using HPCloud.Objects.Domain.Compute;
using HPCloud.Objects.Domain.Compute.Networking;
using HPCloud.Objects.DataAccess.Security;

namespace HPCloud.Objects.DataAccess.Compute
{    
    public class CreateInstanceEventArgs
    {
        private string _message;

        public string Message
        {
            get { return _message; }
            set { _message = value; }
        }
        public CreateInstanceEventArgs(string message)
        {
            _message = message;
        }
    }

    public class WindowsInstanceBuilder
    {
        const string cRDPSecurityGroupName = "rdp_group";
        BaseRepositoryFactory _repositoryFactory = null;
        Settings _settings;
        public delegate void CreateInstanceEventHandler(object sender, CreateInstanceEventArgs e);
        public event CreateInstanceEventHandler Changed;

//=========================================================================================
/// <summary>
/// 
/// </summary>
//=========================================================================================
        public WindowsInstanceBuilder(BaseRepositoryFactory repositoryFactory, Settings settings)
        {
            _settings          = settings;
            _repositoryFactory = repositoryFactory;
        }
//=========================================================================================
/// <summary>
/// 
/// </summary>
//=========================================================================================
        public WindowsInstanceBuilder()
        {}
//=========================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="e"></param>
//=========================================================================================
        private void OnChanged(CreateInstanceEventArgs e)
        {
            if (Changed != null)
                Changed(this, e);
        }
//=========================================================================================
/// <summary>
/// 
/// </summary>
//=========================================================================================
        public Settings Settings
        {
            get { return _settings; }
            set { _settings = value; }
        }
//=========================================================================================
/// <summary>
/// 
/// </summary>
//=========================================================================================
        public BaseRepositoryFactory RepositoryFactory
        {
            get { return _repositoryFactory; }
            set { _repositoryFactory = value; }
        }
//=========================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="server"></param>
//=========================================================================================
        public HPCloud.Objects.Domain.Compute.Server CreateInstance(NewServer server)
        {
            ISecurityRepository securityRepository = this.RepositoryFactory.CreateSecurityRepository();
            IServerRepository serverRepository     = this.RepositoryFactory.CreateServerRepository();

            // Make sure the instance is associated with the RDP SecurityGroup, opening up the correct rdp ports..

            server.SecurityGroups.Add(this.CreateRDPSecurityGroupAssignment());

            // Create the instance itself..

            this.OnChanged(new CreateInstanceEventArgs("- Creating Server Instance."));
            HPCloud.Objects.Domain.Compute.Server result = serverRepository.SaveServer(server);
            this.OnChanged(new CreateInstanceEventArgs("- Instance created successfully."));
            
            // Grab the newly assigned Administrator password from the instances console log and stash
            // it for later..
            
            string pw = this.GetAdminPassword(result.Id, server.KeyName);

            if (pw != null)
            {
                this.OnChanged(new CreateInstanceEventArgs(""));
                this.OnChanged(new CreateInstanceEventArgs("- Administrator password is " + pw));
                this.OnChanged(new CreateInstanceEventArgs(""));
                
                // Save the new Credentials..
                
                CredentialListManager credManager = new CredentialListManager(this.Settings);
                credManager.SetPassword(result.Id, pw);
                
                Console.WriteLine("");
                Console.WriteLine(@"Would you like to start a remote session with this instance? (Y\N) ");
                Console.WriteLine("");
                ConsoleKeyInfo info = Console.ReadKey();

                if (info.Key == ConsoleKey.Y)
                {
                    HPCloud.Objects.Domain.Compute.Server newServer = serverRepository.GetServer(result.Id);

                    RDPClient client = new RDPClient();
                    client.Address   = newServer.Addresses.Private[1].Addr;
                    client.Password  = pw;
                    client.Username  = "Administrator";

                    client.LaunchClient();
                }
                else
                    return result;
            }
            else
            {
                ConsoleColor oldColor = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("");
                Console.WriteLine(@"Unable to retrieve the Administrator password for this instance. Please try again later using the Get-Password cmdlet. ");
                Console.WriteLine("If you created this server from a Windows image snapshot, use the same Administrator password as the host instance.");
                Console.WriteLine("");
                Console.ForegroundColor = oldColor;
                return null;
            }

            return null;
        }
//=========================================================================================
/// <summary>
/// 
/// </summary>
//=========================================================================================
        private string GetAdminPassword(string serverId, string privateKeyFile)
        {
            this.OnChanged(new CreateInstanceEventArgs(""));
            this.OnChanged(new CreateInstanceEventArgs("- Retrieving Instance Log. This may take several attempts until the Server is in the Ready State."));
            this.OnChanged(new CreateInstanceEventArgs(""));

            GetServerLogAction action = new GetServerLogAction();
            action.ServerId           = serverId;
            action.Length             = "400";
            Log log                   = null;
                       
            // Poll the server for console log changes. When the complete log is available, extract the Administrator password out and send that up the stack..

            for (int i = 1; i < Convert.ToInt16(this.Settings.LogReadAttemptsMax); i++)
            {
                this.OnChanged(new CreateInstanceEventArgs("  Attempt #" + Convert.ToString(i) + " started at " + DateTime.Now.ToString()));
                log = this.RepositoryFactory.CreateServerRepository().GetServerLog(action);

                if (log.Content.Length != 14 && log.Content.Contains("-----END BASE64-ENCODED ENCRYPTED PASSWORD-----") != false)
                {
                    this.OnChanged(new CreateInstanceEventArgs(""));
                    this.OnChanged(new CreateInstanceEventArgs("Processing Instance Log."));
                    return log.ExtractAdminPassword(this.Settings.KeyStoragePath + @"\HP\" + privateKeyFile + ".pem");
                }
                else
                    Thread.Sleep(Convert.ToInt32(this.Settings.LogReadAttemptIntervalInMilliSeconds));
            }
            return null;
        }
//=========================================================================================
/// <summary>
/// Ensures that Port 3389 is open for business..
/// </summary>
//=========================================================================================
        private void CreateRDPPortRule(string groupId)
        {
            ISecurityRepository repository = this.RepositoryFactory.CreateSecurityRepository();
            NewSecurityRule rule            = new NewSecurityRule();

            rule.FromPort      = "3389";
            rule.ToPort        = "3389";
            rule.Name          = "rdpRule";
            rule.IpProtocol    = "tcp";
            rule.ParentGroupId = groupId; 

            SecurityGroupRule newRule = repository.SaveSecurityRule(rule);
        }
//=========================================================================================
/// <summary>
/// This will create the RDP SG if it doesn't yet exist.
/// </summary>
//=========================================================================================
        private SecurityGroupAssignment CreateRDPSecurityGroupAssignment()
        {
            ISecurityRepository repository     = this.RepositoryFactory.CreateSecurityRepository();
            List<SecurityGroup> groups         = repository.GetSecurityGroups();
            SecurityGroupAssignment assignment = null;

            // The RDP Security Group already exist..

            if (groups.Where(g => g.Name.Contains(cRDPSecurityGroupName)).Any())
            {
                this.OnChanged(new CreateInstanceEventArgs(""));
                this.OnChanged(new CreateInstanceEventArgs("- Assigning pre-existing RDP Security Group."));
                assignment = new SecurityGroupAssignment(cRDPSecurityGroupName);
            }
            else
            {
                // We need to create a new RDP Security Group so the User can Remote Desktop into this new instance..

                NewSecurityGroup group = new NewSecurityGroup();
                group.Description      = "Security Group enabling the Remote Desktop Protocol";
                group.Name             = cRDPSecurityGroupName;

                this.OnChanged(new CreateInstanceEventArgs("- Creating new RDP Security Group and RDP Port Rules."));
                SecurityGroup rdpSecurityGroup = repository.SaveSecurityGroup(group);
                this.CreateRDPPortRule(rdpSecurityGroup.Id);

                this.OnChanged(new CreateInstanceEventArgs("- Assigment complete."));
                assignment = new SecurityGroupAssignment(cRDPSecurityGroupName);
            }
            return assignment;
        }
    }
}
